package org.codehaus.mojo.xmlbeans; /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.DependencyResolutionRequiredException; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.project.MavenProject; import org.apache.xmlbeans.impl.tool.SchemaCompiler; import org.codehaus.plexus.util.DirectoryScanner; import org.codehaus.plexus.util.FileUtils; import org.xml.sax.EntityResolver; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; /** * <p/> * A Maven 2 plugin which parses xsd files and produces a corresponding object * model based on the Apache XML Beans parser. * </p> * <p/> * The plugin produces two sets of output files referred to as generated sources * and generated classes. The former is then compiled to the build * <code>outputDirectory</code>. The latter is generated in this directory. * </p> * <p/> * Note that the descriptions for the goal's parameters have been blatently * copied from http://xmlbeans.apache.org/docs/2.0.0/guide/antXmlbean.html for * convenience. * </p> * * @author <a href="mailto:brett@apache.org">Brett Porter</a> * @author <a href="mailto:kris.bravo@corridor-software.us">Kris Bravo</a> * @version $Id$ * @noinspection UnusedDeclaration */ public abstract class AbstractXmlBeansPlugin extends AbstractMojo implements PluginProperties { /** * Define the name of the jar file created. For instance, "myXMLBean.jar" * will output the results of this task into a jar with the same name. * * @parameter */ private File outputJar; /** * Define a set of Namespaces for which to ignore duplicate errors. * * @parameter */ private Set mdefNamespaces; /** * Set to true to permit the compiler to download URLs for imports and * includes. Defaults to false, meaning all imports and includes must be * copied locally. * * @parameter default-value="false" */ private boolean download; /** * Indicates whether source should be compiled with debug information; * defaults to off. If set to off, -g:none will be passed on the command * line for compilers that support it (for other compilers, no command line * argument will be used). If set to true, the value of the debug level * attribute determines the command line argument. * * @parameter default-value="false" */ private boolean debug; /** * The initial size of the memory for the underlying VM, if javac is run * externally; ignored otherwise. Defaults to the standard VM memory * setting. (Examples: 83886080, 81920k, or 80m) * * @parameter */ private String memoryInitialSize; /** * The maximum size of the memory for the underlying VM, if javac is run * externally; ignored otherwise. Defaults to the standard VM memory * setting. (Examples: 83886080, 81920k, or 80m) * * @parameter */ private String memoryMaximumSize; /** * The compiler implementation to use. If this attribute is not set, the * value of the build.compiler property, if set, will be used. Otherwise, * the default compiler for the current VM will be used. * * @parameter */ private String compiler; /** * Controls the amount of build message output. * * @parameter default-value="false" */ private boolean verbose; /** * Supress the normal amount of console output. * * @parameter default-value="true" */ private boolean quiet = true; /** * Do not enforce the unique particle attribution rule. * * @parameter default-value="false" */ private boolean noUpa; /** * Do not enforce the particle valid (restriction) rule. * * @parameter default-value="false" */ private boolean noPvr; /** * Todo: Unkown use. * * @parameter default-value="false" */ private boolean jaxb; /** * Don't compile the generated source files. * * @parameter default-value="false" */ private boolean noJavac; /** * Ignore annotations * * @parameter default-value="false" */ private boolean noAnn; /** * do not validate documentation elements * * @parameter default-value="false" */ private boolean noVDoc; /** * The location of the catalog used to resolve xml entities. * * @parameter expression="${xmlbeans.catalogLocation}" * default-value="${basedir}/src/main/catalog/resolver-catalog.xml" */ protected File catalogLocation; /** * A <code>List</code> of source schema files. * * @parameter */ private List sourceSchemas; /** * Configuration files used by the object generator. For more information * about the format of these files, see Todo. * * @parameter */ private List xmlConfigs; /** * Returns the javasource parameter which specifies an option to the * XmlBeans code generator. * * @parameter * @return null. */ private String javaSource; /** * @parameter expression="${project.artifactMap}" * @required * @readonly */ private Map artifactMap; /** * A reference to the Maven Project metadata. * * @parameter expression="${project}" * @required * @readonly */ protected MavenProject project; /** * Used to find resources used by the XML compiler. Currently not passed to * the compiler, since everything is on the classpath. */ private EntityResolver entityResolver = null; /** * */ protected static final File[] EMPTY_FILE_ARRAY = new File[0]; /** * Files to parse and generate models for. */ private File[] xsdFiles; private File[] wsdlFiles; /** * Empty constructor for the XML Beans plugin. */ public AbstractXmlBeansPlugin() { } /** * <p/> * Map the parameters to the schema compilers parameter object, make sure the necessary output directories exist, * then call on the schema compiler to produce the java objects and supporting resources. * </p> * * @throws MojoExecutionException Errors occurred during compile. * @number MOJO-270 */ public final void execute() throws MojoExecutionException { if ( hasSchemas() ) { try { SchemaCompiler.Parameters compilerParams = ParameterAdapter.getCompilerParameters( this ); boolean stale = isOutputStale(); if ( stale ) { try { compilerParams.getSrcDir().mkdirs(); boolean result = SchemaCompiler.compile( compilerParams ); if ( !result ) { StringBuffer errors = new StringBuffer(); for ( Iterator iter = compilerParams.getErrorListener().iterator(); iter.hasNext(); ) { Object o = iter.next(); errors.append( "xml Error" ).append( o ); errors.append( "\n" ); } throw new XmlBeansException( XmlBeansException.COMPILE_ERRORS, errors.toString() ); } touchStaleFile(); } catch ( IOException ioe ) { throw new XmlBeansException( XmlBeansException.STALE_FILE_TOUCH, getStaleFile().getAbsolutePath(), ioe ); } } else if ( getLog().isInfoEnabled() ) { getLog().info( "All schema objects are up to date." ); } updateProject( project, compilerParams, stale ); } catch ( DependencyResolutionRequiredException drre ) { throw new XmlBeansException( XmlBeansException.CLASSPATH_DEPENDENCY, drre ); } } else if ( getLog().isInfoEnabled() ) { getLog().info( "Nothing to generate." ); } } /** * Indicates whether or not there are schemas to compile. * * @return true if there are schema files in the source or artifacts. * @throws XmlBeansException if we cannot determine if there are xsd files */ private boolean hasSchemas() throws XmlBeansException { int xsds = getXsdFiles().length; int wsdls = getWsdlFiles().length; if ( getLog().isDebugEnabled() ) { getLog().debug( "Number of XSD Files: " + xsds ); getLog().debug( "Number of WSDL Files: " + wsdls ); } return xsds > 0 || wsdls > 0; } protected abstract void updateProject( MavenProject project, SchemaCompiler.Parameters compilerParams, boolean stale ) throws DependencyResolutionRequiredException, XmlBeansException; protected abstract List getXsdJars(); protected abstract File getGeneratedSchemaDirectory(); private void touchStaleFile() throws IOException { File staleFile = getStaleFile(); if ( !staleFile.exists() ) { staleFile.getParentFile().mkdirs(); staleFile.createNewFile(); if ( getLog().isDebugEnabled() ) { getLog().debug( "Stale flag file created." ); } } else { staleFile.setLastModified( System.currentTimeMillis() ); } } /** * @return True if xsd or wsdl files have been modified since the last build (newer than the * <code>staleFlag</code> file). * @throws XmlBeansException if we cannot locate one of the xsd or wsdl files */ private boolean isOutputStale() throws XmlBeansException { File staleFile = getStaleFile(); boolean stale = !staleFile.exists(); if ( !stale ) { if ( getLog().isDebugEnabled() ) { getLog().debug( "Stale flag file exists." ); } long staleMod = staleFile.lastModified(); // check xsds. if ( getLog().isDebugEnabled() ) { getLog().debug( "Comparing to xsd's modification time." ); } final File[] sourceXsds = getXsdFiles(); int fileCount = sourceXsds.length; if ( getLog().isDebugEnabled() ) { getLog().debug( fileCount + " xsd to compare." ); } for ( int i = 0; i < fileCount; i++ ) { if ( sourceXsds[i].lastModified() > staleMod ) { if ( getLog().isDebugEnabled() ) { getLog().debug( sourceXsds[i].getName() + " is newer than the stale flag file." ); } stale = true; } } // check wsdls if ( getLog().isDebugEnabled() ) { getLog().debug( "Comparing to wsdl's modification time." ); } final File[] sourceWsdls = getWsdlFiles(); fileCount = sourceWsdls.length; if ( getLog().isDebugEnabled() ) { getLog().debug( fileCount + " wsdl to compare." ); } for ( int i = 0; i < fileCount; i++ ) { if ( sourceWsdls[i].lastModified() > staleMod ) { if ( getLog().isDebugEnabled() ) { getLog().debug( sourceWsdls[i].getName() + " is newer than the stale flag file." ); } stale = true; } } } return stale; } public abstract File getBaseDir(); public abstract File getStaleFile(); public abstract File getDefaultXmlConfigDir(); /** * Returns the directory where the schemas are located. Note that this is * the base directory of the schema compiler, not the maven project. * * @return The schema directory. */ public abstract File getSchemaDirectory(); /** * Returns a classpath for the compiler made up of artifacts from the * project. * * @return Array of classpath entries. * @throws DependencyResolutionRequiredException Plugin wasn't annotated with the right requiresDependencyResolution */ public abstract File[] getClasspath() throws DependencyResolutionRequiredException; /** * Returns null. Currently the compiler preference isn't passwed to the xml * beans compiler. * * @return null. */ public final String getCompiler() { return compiler; } /** * Returns configuration files identified in the xmlConfigs string passed by * the project configuration. If none were identified, a check is made for * the default xsd config directory src/xsdconfig. * * @return An array of configuration files. */ public final File[] getConfigFiles() throws XmlBeansException { File defaultXmlConfigDir = getDefaultXmlConfigDir(); if ( getLog().isDebugEnabled() ) { getLog().debug( "Creating a list of config files." ); } try { if ( xmlConfigs != null ) { return ( File[] ) getFileList( xmlConfigs ).toArray( new File[]{} ); } else if ( defaultXmlConfigDir.exists() ) { if ( getLog().isDebugEnabled() ) { getLog().debug( "Examining " + defaultXmlConfigDir + " for config files." ); } List defaultDir = new ArrayList(); defaultDir.add( defaultXmlConfigDir ); return ( File[] ) getFileList( defaultDir ).toArray( EMPTY_FILE_ARRAY ); } else { return null; } } catch ( XmlBeansException xmlbe ) { throw new XmlBeansException( XmlBeansException.INVALID_CONFIG_FILE, xmlbe ); } } /** * Recursively travers the file list and it's subdirs and produce a single * flat list of the files. * * @param fileList list of files * @return files */ private List getFileList( List fileList ) throws XmlBeansException { if ( fileList != null ) { if ( getLog().isDebugEnabled() ) { getLog().debug( "A list was given." ); } List files = new ArrayList(); File nextFile; DirectoryScanner scanner = new DirectoryScanner(); // String[] includes = {"**/*"}; // scanner.setIncludes(includes); scanner.setCaseSensitive( false ); scanner.addDefaultExcludes(); for ( Iterator iterator = fileList.iterator(); iterator.hasNext(); ) { nextFile = ( File ) iterator.next(); if ( nextFile.exists() ) { if ( nextFile.isDirectory() ) { scanner.setBasedir( nextFile ); scanner.scan(); String[] fileArray = scanner.getIncludedFiles(); if ( fileArray != null ) { for ( int i = 0; i < fileArray.length; i++ ) { if ( getLog().isDebugEnabled() ) { getLog().debug( "Adding " + fileArray[i] ); } files.add( new File( nextFile, fileArray[i] ) ); } } } else { files.add( nextFile ); } } else { throw new XmlBeansException( XmlBeansException.MISSING_FILE, nextFile .getAbsolutePath() ); } } return files; } else { if ( getLog().isDebugEnabled() ) { getLog().debug( "No list was given. Returning." ); } return null; } } /** * Returns a null entity resolver. * * @return entityResolver set to null. */ public final EntityResolver getEntityResolver() { return entityResolver; } /** * Returns an empty collection the compiler will store error message Strings * in. * * @return An empty ArrayList. */ public final Collection getErrorListeners() { return new ArrayList(); } /** * Todo: Not certain of the purpose of this. * * @return null at this time. */ public final List getExtensions() { return null; } /** * Used during testing. * * @param project the maven project we are setting. */ final void setProject( MavenProject project ) { this.project = project; } /** * An array of other source files. Currently an empty array. * * @return An empty file array. */ public final File[] getJavaFiles() { return new File[]{}; } /** * Returns null at this time. Passed to the schema compiler. * * @return null. */ public final Set getMdefNamespaces() { return mdefNamespaces; } /** * * * */ public final String getJavaSource() { return javaSource; } /** * Returns the initial size of the memory allocation for the schema compile * process. * * @return The initial memory size value. */ public final String getMemoryInitialSize() { return memoryInitialSize; } /** * Returns the maximum size of the memory allocation for the schema compile * process. * * @return The max memory size value. */ public final String getMemoryMaximumSize() { return memoryMaximumSize; } /** * Returns null at this time. This is passed to the schema compiler. * * @return null. */ public final String getName() { return null; } /** * Returns the location of the output jar file should one be produced. If * it has been set, make sure the directories exist before passing it * to the xml beans compiler. * * @return The jar file location. * @number MXMLBEANS-17 */ public final File getOutputJar() { if ( outputJar != null ) { outputJar.getParentFile().mkdirs(); } return outputJar; } /** * Todo: Not certain of the purpose of this. * * @return null at this time. */ public final String getRepackage() { return null; } /** * Returns the name of the file used to resolve xml entities. * * @return The entity resolver catalog file location. * @number MXMLBEANS-3 */ public final boolean hasCatalogFile() { if ( getLog().isDebugEnabled() ) { getLog().debug( "looking for resolver catalog at " + catalogLocation.getAbsolutePath() ); } return catalogLocation.exists(); } /** * Returns the name of the file used to resolve xml entities. * * @return The entity resolver catalog file location. * @number MXMLBEANS-3 */ public final String getCatalogFile() { if ( getLog().isDebugEnabled() ) { getLog().debug( "Using resolver catalog." ); } return catalogLocation.getAbsolutePath(); } /** * Returns a file array of xsd files to translate to object models. * * @return An array of schema files to be parsed by the schema compiler. * @number MXMLBEANS-21 */ public final File[] getXsdFiles() throws XmlBeansException { xsdFiles = getFiles( xsdFiles, "**/*.xsd" ); return xsdFiles; } /** * Returns a file array of wsdl files to translate to object models. * * @return An array of wsdl files to be parsed by the schema compiler. */ public final File[] getWsdlFiles() throws XmlBeansException { wsdlFiles = getFiles( wsdlFiles, "**/*.wsdl" ); return wsdlFiles; } private File[] getFiles( final File[] schemaFiles, final String includeFilter ) throws XmlBeansException { // Already got files once before, so no need to do the work again if ( schemaFiles != null ) { return schemaFiles; } final List schemas = new ArrayList(); File schemaDirectory = getSchemaDirectory(); if ( getLog().isDebugEnabled() ) { getLog().debug( "The schema Directory is " + schemaDirectory ); } // if list of schemas to process exists, add schemas from xsdJars and schemaDirectory only if ( sourceSchemas != null ) { // collect schemas from artifacts Map artifactSchemas = getArtifactSchemas(); File nextFile; for ( Iterator iterator = sourceSchemas.iterator(); iterator.hasNext(); ) { String schemaName = (String) iterator.next(); String ext = FileUtils.getExtension( schemaName ); if ( !includeFilter.endsWith( ext ) ) { continue; } nextFile = new File( schemaDirectory, schemaName ); if ( nextFile.exists() ) { // add schema if it exists in schemaDirectory schemas.add( nextFile ); } else if ( artifactSchemas.containsKey( schemaName ) ) { // add schema if it's in an artfact schemas.add( artifactSchemas.get( schemaName ) ); } else { // throw exception if schema can't be found String[] fields = new String[3]; fields[0] = schemaName; fields[1] = schemaDirectory.getAbsolutePath(); fields[2] = ( artifactMap.isEmpty() ? "" : " or the schema artifact(s)" ); throw new XmlBeansException( XmlBeansException.MISSING_SCHEMA_FILE, fields ); } } return (File[]) schemas.toArray( new File[schemas.size()] ); } // if list of schemas to process was not provided // add all schemas from xsdJars that match filter Map artifactSchemas = getArtifactSchemas(); if ( !artifactSchemas.isEmpty() ) { File nextFile; for ( Iterator fileIterator = artifactSchemas.values().iterator(); fileIterator.hasNext(); ) { nextFile = (File) fileIterator.next(); String ext = FileUtils.getExtension( nextFile.getName() ); if ( includeFilter.endsWith( ext ) ) { schemas.add( nextFile ); } } } // add all schemas from xsdJars that match filter if ( schemaDirectory.exists() ) { DirectoryScanner scanner = new DirectoryScanner(); scanner.setBasedir( schemaDirectory ); if ( getLog().isDebugEnabled() ) { getLog().debug( "Scanning for " + includeFilter ); } String[] includes = {includeFilter}; scanner.setIncludes( includes ); scanner.addDefaultExcludes(); scanner.setCaseSensitive( false ); scanner.scan(); String[] files = scanner.getIncludedFiles(); if ( files != null ) { for ( int i = 0; i < files.length; i++ ) { if ( getLog().isDebugEnabled() ) { getLog().debug( "Adding " + files[i] ); } schemas.add( new File( schemaDirectory, files[i] ) ); } } } return (File[]) schemas.toArray( new File[schemas.size()] ); } /** * Sweep through the jar artifacts which contain xsds and produce a list of * paths to each xsd within the file. Leave it up to the entity resolver to * pass the actual file to the compiler. * * @return A list of path's to the XSD's in the artifact jars. This doesn't * include the jar paths. * @number MXMLBEANS-21 */ private Map getArtifactSchemas() throws XmlBeansException { if ( getLog().isDebugEnabled() ) { getLog().debug( "Artifact count: " + artifactMap.size() ); } SchemaArtifactLookup lookup = new SchemaArtifactLookup( artifactMap, getLog() ); Map artifactSchemas = new HashMap(); List xsdJars = getXsdJars(); File prefix = getGeneratedSchemaDirectory(); int count = xsdJars.size(); // Collect the file paths to the actual jars Artifact nextArtifact; if ( getLog().isDebugEnabled() ) { getLog().debug( "looking for artifact schemas." ); } for ( int i = 0; i < count; i++ ) { if ( getLog().isDebugEnabled() ) { getLog().debug( "resolving " + xsdJars.get( i ) + " into a file path." ); } nextArtifact = lookup.find( ( String ) xsdJars.get( i ) ); artifactSchemas.putAll( SchemaArtifact.getFilePaths( nextArtifact, getLog(), prefix ) ); } return artifactSchemas; } /** * Returns the state of debuggin. * * @return true if debug mode. */ public final boolean isDebug() { return debug; } /** * Returns true if dependencies are to be downloaded by the schema compiler. * * @return true if resources should be downloaded. */ public final boolean isDownload() { return download; } /** * Returns true if jaxb is set. * * @return true if the jaxb flag on the schema compiler should be set. */ public final boolean isJaxb() { return jaxb; } public final boolean isNoAnn() { return noAnn; } public final boolean isNoVDoc() { return noVDoc; } /** * Returns True if generated source files are not to be compiled. * * @return true if no compiling should occur. */ public final boolean isNoJavac() { return noJavac; } /** * Do not enforce the particle valid (restriction) rule if true. * * @return true if no enforcement should occur. */ public final boolean isNoPvr() { return noPvr; } /** * If true, do not enforce the unique particle attribution rule. * * @return particle attibution enforcement */ public final boolean isNoUpa() { return noUpa; } /** * Returns true if the schema compiler should reduce verbosity. * * @return true if message suppression is on. */ public final boolean isQuiet() { return quiet; } /** * Returns true if the schema compiler should increase verbosity. * * @return true if verbose mode is on. */ public final boolean isVerbose() { return verbose; } /** * No validation beyond those done by the maven plugin occur at this time. * * @throws XmlBeansException Currently not used. */ public final void validate() throws XmlBeansException { } }